#!/bin/bash
# *************************************
# 功能: K8s集群初始化所依赖的功能函数
# 作者: 王树森
# 联系: wangshusen@sswang.com
# 版本: v0.1
# 日期: 2023-07-31
# *************************************


# 定制CentOS 软件源函数
centos_repo(){
  # 定制centos环境软件源
  [ -f "/tmp/${centos_repo_file}" ] && rm -f "/tmp/${centos_repo_file}"
  cat > "/tmp/${centos_repo_file}" <<-eof
[kubernetes]
name=Kubernetes
baseurl=https://${k8s_sources_repo_addr}/kubernetes/yum/repos/kubernetes-el7-\$basearch
enabled=1
gpgcheck=0
eof
}

# 定制Ubuntu 软件源函数
ubuntu_repo(){
  # 定制ubuntu环境软件源
  [ -f "/tmp/${ubuntu_repo_file}" ] && rm -f "/tmp/${ubuntu_repo_file}"
  cat >"/tmp/${ubuntu_repo_file}"<<-eof
deb https://${k8s_sources_repo_addr}/kubernetes/apt/ kubernetes-xenial main
eof
}

# 定制软件源函数
create_repo(){
  # 思考：根据远程目标主机的类型自动判断生成软件源的能力
  #       暂时不做，我们通过传参的方式，指定系统类型和目标主机
  #       问题：如何拆分传参中的第一个参数 和 后续其他参数
  #             echo $args | awk 'sub($1" ","")'
  
  # 接收参数
  local os_type="$1"
  local host_list=$(echo $* | awk 'sub($1" ","")')

  # 根据系统类型执行各自的函数
  if [ "${os_type}" == "Ubuntu" ]; then
    # 根据目标主机传递生成的源文件
    ubuntu_repo
    for i in ${host_list}
    do
       scp "/tmp/${ubuntu_repo_file}" "${login_user}@$i:${ubuntu_repo_dir}/${ubuntu_repo_file}"
       # 更新目标主机的软件源信息
       get_keyfile="curl -s https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add -"
       ssh "${login_user}@$i" "${get_keyfile}; ${cmd_type} update -y"
       echo -e "\e[32mkubernetes集群主机 $i 的软件源配置完毕 \e[0m"
    done
  else
    # 根据目标主机传递生成的源文件
    centos_repo
    for i in ${host_list}
    do
       scp "/tmp/${centos_repo_file}" "${login_user}@$i:${centos_repo_dir}/${centos_repo_file}"
       # 更新目标主机的软件源信息
       ssh "${login_user}@$i" "${cmd_type} makecache fast -y"
       echo -e "\e[32mkubernetes集群主机 $i 的软件源配置完毕 \e[0m"
    done
  fi
  

}

# k8s软件版本获取函数
k8s_version(){
  # 获取参数
  local os_type="$1"
  local host_addr="$2"
  
  # 到远程主机执行获取版本的命令
  echo -e "\e[32m当前环境下可安装的kubernetes版本: \e[0m"
  # tag_line="=========================================================================================================="
  # echo "${tag_line}"
  echo_tag "=" 106
  if [ "${os_type}" == "Ubuntu" ];then
    ssh "${login_user}@${host_addr}" "apt-cache madison kubeadm | head -10"
  else
    ssh "${login_user}@${host_addr}" "yum list --showduplicates kubeadm | tail -10 | sort -r"
  fi
  # echo "${tag_line}"
  echo_tag "=" 106
}

# 离线安装函数
k8s_install_offline(){
  echo "离线安装k8s"
}

# 在线安装函数
k8s_install_online(){
  # 接收参数
  # 需求：根据后端操作系统不同 采用不同的命令格式
  local os_type="$1"
  local node_list=$(echo $* | awk 'sub($1" ","")')
  [ "${os_type}" == "Ubuntu" ] && cmd_type="apt" || cmd_type="yum"

  # 获取需要安装的版本
  # 优化1: 防止因为用户交互导致k8s软件部署中断，这里采用等待方式
  read -t 10 -p "请输入要部署的k8s版本(比如：1.28.1，空表示最新版-默认): " version
  version="${version:-}"
  if [ -n "${version}" ];then
    # 判断操作系统类型
    [ "${os_type}" == "Ubuntu" ] && soft_tail='='${version}'-00' || soft_tail='-'${version}'-0'
    full_cmd="${cmd_type} install -y kubeadm${soft_tail} kubectl${soft_tail} kubelet${soft_tail}; systemctl enable kubelet; systemctl start kubelet" 
  else
    full_cmd="${cmd_type} install -y kubeadm kubectl kubelet; systemctl enable kubelet; systemctl start kubelet"
  fi 

  # 安装软件
  for i in $node_list;
  do
    ssh "${login_user}@${i}" "${full_cmd}"
    echo -e "\e[32mkubernets节点 $i 软件安装完毕!!!\e[0m"
  done

}
# 安装软件函数
k8s_install(){
  # 接收参数
  local os_type="$1"
  local install_type="$2"
  local node_list="$3"

  # 判断是在线还是离线
  if [ "${install_type}" == "online" ];then
    k8s_install_online "${os_type}" "${node_list}"
  else 
    k8s_install_offline "${os_type}" "${node_list}"
  fi
}


# 提交镜像到harbor
push_image(){
  # 接收参数
  local is_use_offline_image="$1"
  local images_list="$2"
  
  # master节点登录harbor仓库
  harbor_login_cmd="docker login ${harbor_url} -u ${harbor_user} -p ${harbor_passwd}"
  ssh ${login_user}@${master_host} "${harbor_login_cmd}"
  
  # 镜像提交动作
  for i in ${images_list};do
    # 判断离线在线的提交命令
    if [ "${is_use_offline_image}" == "yes" ];then
      docker_push_cmd="docker push $i"
    else
      docker_push_cmd="docker pull ${ali_mirror}/$i; docker tag ${ali_mirror}/$i ${harbor_addr}/${harbor_k8s_repo}/$i; docker push ${harbor_addr}/${harbor_k8s_repo}/$i; docker rmi ${ali_mirror}/$i"
    fi
    # 注意：我们目前的容器引擎是docker，后续使用containerd的时候，这里的命令需要调整
    ssh ${login_user}@${master_host} "${docker_push_cmd}" 2>/dev/null && push_status="ok"
    if [ "${push_status}" == "ok" ]; then
      echo -e "\e[32m指定版本的 $i 镜像文件推送到harbor镜像仓库: 成功!!!\e[0m"
    else
      echo -e "\e[31m指定版本的 $i 镜像文件推送到harbor镜像仓库: 失败!!!\e[0m"
    fi
  done 
}

# 在线方式获取镜像
get_images_online(){
  # 获取参数
  local is_use_offline_image="$1"
  local k8s_version="$2"
  local master_host="$3"

  # 获取指定k8s环境的镜像文件
  get_image_cmd="kubeadm config images list --kubernetes-version=${k8s_version}"
  images_list=$(ssh ${login_user}@${master_host} "${get_image_cmd}" | awk -F'/' '{print $NF}')
  # 提交镜像到harbor
  push_image "${is_use_offline_image}" "${images_list}"

  # master节点登录harbor仓库
  # harbor_login_cmd="docker login ${harbor_url} -u ${harbor_user} -p ${harbor_passwd}"
  # ssh ${login_user}@${master_host} "${harbor_login_cmd}"
  # echo -e "\e[32m直接从互联网获取指定版本的镜像文件\e[0m"
  # for i in ${images_list}  # 这里不要加双引号 
  # do
    # 注意：我们目前的容器引擎是docker，后续使用containerd的时候，这里的命令需要调整
  #   ssh ${login_user}@${master_host} "docker pull ${ali_mirror}/$i; docker tag ${ali_mirror}/$i ${harbor_addr}/${harbor_k8s_repo}/$i; docker push ${harbor_addr}/${harbor_k8s_repo}/$i; docker rmi ${ali_mirror}/$i"
  #   echo -e "\e[32m指定版本的 $i 镜像文件已推送到harbor镜像仓库\e[0m"
  # done
}

# 离线方式获取镜像
get_images_offline(){
  # 获取参数
  local is_use_offline_image="$1"
  local k8s_version="$2"
  local master_host="$3"

  # 检测本地是否存在镜像文件
  k8s_images_file="${images_dir}/${k8s_cluster_images}"
  if [ -f "${k8s_images_file}" ]; then
    # 传输镜像文件到master主机
    ssh ${login_user}@${master_host} "ls /tmp/${k8s_cluster_images}" 2>&1 >>/dev/null && file_status='ok'
    [ "$file_status" != "ok" ] && scp_file ${master_host} "${k8s_images_file}" "/tmp"
    # 导入镜像到master主机
    ssh ${login_user}@${master_host} "docker load < /tmp/${k8s_cluster_images}"
    # 获取要提交镜像名称
    get_image_cmd="docker images"
    images_list=$(ssh ${login_user}@${master_host} "${get_image_cmd}" | awk '/sswang/{print $1":"$2}')
    # 提交镜像到harbor
    push_image "${is_use_offline_image}" "${images_list}"
    
    # harbor_login_cmd="docker login ${harbor_url} -u ${harbor_user} -p ${harbor_passwd}"
    # ssh ${login_user}@${master_host} "${harbor_login_cmd}"

    # for i in ${images_list}  # 这里不要加双引号
    # do
      # 注意：我们目前的容器引擎是docker，后续使用containerd的时候，这里的命令需要调整
      # ssh ${login_user}@${master_host} "docker push $i" 2>/dev/null && push_status="ok"
      # if [ "${push_status}" == "ok" ]; then
      #   echo -e "\e[32m指定版本的 $i 镜像文件推送到harbor镜像仓库: 成功!!!\e[0m"
      # else
      #   echo -e "\e[31m指定版本的 $i 镜像文件推送到harbor镜像仓库: 失败!!!\e[0m"
      # fi
    # done
    
  else
    # 使用在线的镜像获取逻辑
    local is_use_offline_image="no"
    get_images_online "${is_use_offline_image}" "${k8s_version}" "${master_host}"
  fi
} 


# k8s镜像获取函数
get_images(){
  # 函数使用示例: get_images "${local_repo}" "${master1}"
  
  # 接收参数
  local image_repo="$1"
  local master_host="$2"
  local is_get_image="$3"
  local is_use_offline_image="$4"

  # 获取镜像版本
  # get_k8s_version="kubeadm version | awk -F'\"v' '{print $2}' | awk -F'\"' '{print $1}'"
  [ "${k8s_version}" == "" ] && k8s_version=$(ssh ${login_user}@${master_host} "kubeadm version" | awk -F'\"v' '{print $2}' | awk -F'\"' '{print $1}')
  [ "${image_repo}" == "no" ] && return
  # 交互判断：是否提前获取镜像
  # read -p "请输入是否需要提前获取镜像文件(yes|no): " is_get_image
  if [ "${is_get_image}" == "yes" ]; then
    # 交互判断：是否是否用离线镜像(没办法指定版本)
    # read -p "是否使用本地离线镜像文件(yes|no): " is_use_offline_image
    if [ "${is_use_offline_image}" == "yes" ];then
      echo "传输本地离线镜像文件到目标主机----"
      get_images_offline "${is_use_offline_image}" "${k8s_version}" "${master_host}"
    elif [ "${is_use_offline_image}" == "no" ];then
      get_images_online "${is_use_offline_image}" "${k8s_version}" "${master_host}"
      # 获取指定k8s环境的镜像文件
      # get_image_cmd="kubeadm config images list --kubernetes-version=${k8s_version}"
      # images_list=$(ssh ${login_user}@${master_host} "${get_image_cmd}" | awk -F'/' '{print $NF}')
      # master节点登录harbor仓库
      # harbor_login_cmd="docker login ${harbor_url} -u ${harbor_user} -p ${harbor_passwd}"
      # ssh ${login_user}@${master_host} "${harbor_login_cmd}"
      # echo -e "\e[32m直接从互联网获取指定版本的镜像文件\e[0m"
      # for i in ${images_list}  # 这里不要加双引号 
      # do
      #   # 注意：我们目前的容器引擎是docker，后续使用containerd的时候，这里的命令需要调整
      #   ssh ${login_user}@${master_host} "docker pull ${ali_mirror}/$i; docker tag ${ali_mirror}/$i ${harbor_addr}/${harbor_k8s_repo}/$i; docker push ${harbor_addr}/${harbor_k8s_repo}/$i; docker rmi ${ali_mirror}/$i"
      #   echo -e "\e[32m指定版本的 $i 镜像文件已推送到harbor镜像仓库\e[0m"
      # done
    fi
  elif [ "${is_get_image}" == "no" ];then
    echo "不需要提前获取镜像文件，直接下一步"
  fi
  
}

# k8s集群初始化函数
cluster_create(){
  # 接收参数：主要是是否使用本地镜像仓库的值
  local local_repo_status="$1"
 
  # 定制镜像仓库地址
  [ "${local_repo_status}" == "no" ] && repo_addr="${ali_mirror}" || repo_addr="${harbor_addr}/${harbor_k8s_repo}"

  # 获取k8s集群版本信息
  k8s_version=$(ssh ${login_user}@${master1} "kubeadm version" | awk -F'\"v' '{print $2}' | awk -F'\"' '{print $1}')

  # 构造集群初始化命令(考虑信息的临时存储)
  cluster_init_cmd="kubeadm init --kubernetes-version=${k8s_version} --apiserver-advertise-address=${master1} --image-repository=${repo_addr} --service-cidr=10.96.0.0/12 --pod-network-cidr=10.244.0.0/16 --ignore-preflight-errors=Swap ${cri_options}"
  ssh ${login_user}@${master1} "${cluster_init_cmd}" | tee "/tmp/${cluster_init_msg_file}"
  # 提示信息输出 --cri-socket unix:///var/run/cri-dockerd.sock
  echo -e "\e[33m其他节点添加到当前k8s集群的时候，请在默认命令后添加如下参数：\e[0m"
  echo -e "\e[33m      ${cri_options}\e[0m"
  echo -e "\e[32m\n当前k8s集群master节点初始化成功!!!!!!\e[0m"
}

# master环境收尾函数
k8s_master_tail(){
  # 传递master节点收尾脚本到远程主机
  scp "${ext_scripts_dir}/${k8s_master_tail_scripts}"  ${login_user}@${master1}:/tmp/

  # 远程master节点执行脚本文件
  ssh ${login_user}@${master1} "/bin/bash /tmp/${k8s_master_tail_scripts}"

  # 测试master节点收尾动作
  ssh ${login_user}@${master1} "kubectl get nodes; rm -rf /tmp/${k8s_master_tail_scripts}"
  echo -e "\e[32m\n当前k8s集群master节点初始化收尾动作执行完毕!!!!!!\e[0m"
}

# node加入集群函数
add_k8s_node(){
  # 使用示例：add_k8s_node "${host_role}" "${ip_list}"
  local node_role="$1"
  # node_list=$(echo $* | awk 'sub($1" ","")')
  local node_list="$2"
  # 获取增加节点命令
  if [ "${node_role}" == "master" ];then
    echo "获取master节点初始化命令内容"
  elif [ "${node_role}" == "node" ];then
    msg_txt=$(grep -A2 'm join' "/tmp/${cluster_init_msg_file}")
  fi
  sub_cmd=$(echo ${msg_txt} | sed 's#\\ ##')
  add_node_cmd="${sub_cmd} ${cri_options}"
  for node in ${node_list};
  do
    # 工作节点增加的逻辑
    ssh ${login_user}@$node "${add_node_cmd}"
    # 控制节点增加的逻辑-待考虑
    echo -e "\e[32m节点 $node 加入当前k8s集群完毕!!!\e[0m"
  done
  # 测试节点添加集群效果
  ssh ${login_user}@${master1} "kubectl get nodes"
  echo -e "\e[32m\n所有指定节点都已经加入到k8s集群!!!\e[0m"
}

# 定制flannel解决方案
k8s_network_flannel(){
  # 
  
  # 判断本地是否存在yaml清单文件
  [ -f "${addons_flannel}/${flannel_yaml}" ] || wget "${flannel_url}/${flannel_yaml}" -O "${addons_flannel}/${flannel_yaml}"

  # 将合适的文件模板传递到master主机
  ssh "${login_user}@${master1}" "[ ! -d ${remote_dir} ] && mkdir ${remote_dir} -p; \
         [ -f ${remote_dir}/${flannel_yaml} ] && rm -f ${remote_dir}/${flannel_yaml}"
  scp "${addons_flannel}/${flannel_yaml}" "${login_user}@${master1}:${remote_dir}"

  # 镜像获取和提交
  flannel_image=$(ssh "${login_user}@${master1}" "grep ' image:' ${remote_dir}/${flannel_yaml}" | uniq | awk '{print $NF}')
  for i in ${flannel_image}; do
    image_name=$(echo $i | awk -F '/' '{print $NF}')
    image_new_name="${harbor_addr}/${harbor_k8s_repo}/${image_name}"
    ssh "${login_user}@${master1}" "docker pull $i; \
         docker tag $i ${image_new_name}; \
         docker push ${image_new_name}; \
         docker rmi $i; \
         sed -i \"s#$i#${image_new_name}#g\" ${remote_dir}/${flannel_yaml}"
  done
  # 修改配置文件后应用配置文件并检查效果
  ssh "${login_user}@${master1}" "/usr/bin/kubectl apply -f ${remote_dir}/${flannel_yaml}"
  echo -e "\e[33m\n正在检查当前集群的节点网络状态:\e[0m"
  waiting 10
  echo_tag "=" 50
  ssh "${login_user}@${master1}" "/usr/bin/kubectl get nodes"
  echo_tag "=" 50
}

# 定制calico解决方案
k8s_network_calico(){
  echo "定制calico网络..."
}

# 定制cilium解决方案
k8s_network_cilium(){
  echo "定制cilium网络..."
}

# 定制网络函数
k8s_network_install(){
  # 根据参数，选择不同的解决方案
  # 接收参数
  local net_type="$1"
  
  # 定制网络解决方案
  if [ "${net_type}" == "flannel"  ];then
    k8s_network_flannel
  elif [ "${net_type}" == "calico" ];then
    k8s_network_calico
  elif [ "${net_type}" == "cilium" ];then
    k8s_network_cilium
  else
    echo -e "\e[31m目前该脚本暂不支持其他类型的网络解决方案，有需求可以联系：\e[0m"
    echo -e "\e[31m   抖音号：sswang_yys, B站：自学自讲\n\e[0m"
  fi
  echo -e "\e[32m指定网络解决方案已经部署完毕!!!\n\e[0m"
}

# 一键集群初始化功能函数
onekey_cluster_init(){
  # 接收参数
  # local os_type="$1"
  local remote_status=$(ssh "${login_user}@${master1}" "grep -i ubuntu /etc/issue")
  [ -n "${remote_status}" ] && local os_type="Ubuntu" || local os_type="CentOS"
  # 改造一键环境部署的软件安装和软件源更新的可执行命令
  [ "${os_type}" == "Ubuntu" ] && local cmd_type="apt" || local cmd_type="yum"
  local is_online="$1"
  local local_repo="$2"
  # 执行k8s集群定制软件源操作
  create_repo "${os_type}" "${all_k8s_list}"
  # 执行k8s集群软件安装操作
  k8s_version "${os_type}" "${master1}"
  k8s_install "${os_type}" "${is_online}" "${all_k8s_list}"
  # 执行k8s集群初始化操作
  get_images "${local_repo}" "${master1}" "yes" "yes"
  cluster_create "${local_repo}"
  # 执行k8s集群初始化收尾
  k8s_master_tail
  # 执行node加入K8s集群操作
  add_k8s_node "node" "${k8s_node_list}"
  # 定制K8s集群网络解决方案操作
  k8s_network_install "${default_network_type}"
}
